#!/opt/swipl/bin/swipl -f
% 
% A Prolog KP with SIB caching and client side native PROLOG querying.
%
% Dr. Stefan Rapp 
% (c) 2010,2011
%
%
% Why prolog?
% ===========
%
% Prolog has a long history in artificial intelligence research and is well
% suited to efficiently and elegantly handle semantic structures including RDF
% triples. SWI-PROLOG has already existing and widely used libraries for
% parsing XML, an RDF store, and routines to read/write ontologies directly in
% RDF-XML. It can be licensed as LGPL, and is easily linked to other
% programming languages using a C-style foreign language interface. Although
% PROLOG gained most attention in the 1980s and 1990s, it is still in active
% use and development: for example every Nokia N900 phone ships with an
% SWI-PROLOG engine that does fundamental rule-based resource management. As a
% declarative language, it is comparably easy to modify the KP to re-use the
% code for varying application domains or varying ontologies.
%
% General idea of the implementation
% ==================================
%
% The general idea is to cache the subpart / subontology that the KP is able
% to handle with a single subscription from the SIB. The cache is a local RDF
% store, that is efficiently maintained in the PROLOG engine's memory space.
% consistency between the cache and the SIB is based on the SSAP subscription
% mechanism based on triples. (Insertions are directly inserted in the SIB and 
% the cache is completely refilled, currently.)
%
% Any subsequent reasoning can directly work on this local RDF store without
% latencies otherwise induced by XML handling, networking, SIB load etc.
%
% To ease implementations, PROLOG predicates (small code snippets) can be
% defined that adapt application program structures such as object hierarchies
% or data structures to the knowledge processing and the ontological modeling,
% the general idea being that knowledge processing and ontology treatment is
% fully contained in the PROLOG part, whereas another part of the program
% written in C++ or similar is dealing with visualization, user interface or
% interfacing to hardware.
%
% All XML structurs of the SSAP are - where possible - not treated as strings,
% but always in the standard nested prolog data structure as is produced by
% SWI PROLOG's standard XML parser or consumed by its XML generator. Although
% having various representations for similar things can be confusing, using
% nested prolog structures gives most flexibility for manipulation and thus
% makes it easier to check return values or make extensions to the
% code.
% 
% The low level processing of SSAP messages is encapsulated in module
% iop-sib-access-lo.
%
% The domain/application dependendant part needs to specify which
% parts of the SIB (which triples) should be cached.

%
% USAGE:
% ======

:- module(
	iop_sib_access, 
	[ 
          join_sib/3,           % +Host, +SmartSpaceName, +Port
	  query_sib/0,
	  subscribe_sib/0,
	  unsubscribe_sib/0,
	  process_subscription/0,
	  insert_sib/1,         % +triple(S,P,O)
	  remove_sib/1,         % +triple(S,P,O)
	  leave_sib/0,
	  rdf/3                 % ?S, ?P, ?O
        ]).

%
% the usual pattern is 
%
% - in Section A of your domain.pl define the ontology subpart you are 
%   interested in. 
%
%   cached(triple('...','...','...')).
%   cached(triple('...','...','...')).
%   cached(triple('...','...','...')).
%   ...
%
% - from your C/C++ file, read the domain.pl and call the predicates to 
%   join and subscribe the SIB.
%
%   join(somehostname,'SmartSpaceName',10010),subscribe_sib.
%
% - then do some stuff, reason on the cached rdf etc. you can wrap in 
%   domain specific 'adapter' predicates in Section B of your domain.pl, 
%   or call everything directly from C/C++. 
%
%   chain triples with variables
%   rdf(...,...,A), rdf(A,...,...), ...
%
%   use standard prolog predicates to collect information
%   findall(X, rdf(X,...,...), List)
%
% - From your C/C++, regularly check for incoming changes. This allows 
%   for single threaded designs.
%
%   process_subscriptions.
%
% - you can also insert and remove triples in the SIB
%
%   insert_sib(triple(...,...,...)),
%
%   remove_sib(triple(...,...,...)),
%
% - finally, don't forget to leave the SIB when exiting from C/C++.
%
%   leave_sib.
%


%%% 
%%% Library includes
%%% Taking advantage of SWI-PROLOG's rdf library,including XML processing
%%%

:- use_module('iop_sib_access_low').
:- [library(socket),library('semweb/rdf_db')].


%%%
%%% Highlevel interface to SSAP. It is caller responsibility to
%%% join/leave. Socket connections stays open from join to leave.
%%%

:-dynamic(sib_socket/2).
:-dynamic(sib/3).

join_sib(Host,SmartSpaceName,Port) :- 
	setup_call_catcher_cleanup(tcp_socket(Socket),
                                   tcp_connect(Socket, Host:Port),
				   exception(_),
                                   tcp_close_socket(Socket)),
	tcp_open_socket(Socket, In, Out),
	retractall(sib_socket(_,_)),    % remove any leftovers
	assert(sib_socket(In,Out)),	% remember sockets
	assert(sib(Host,SmartSpaceName,Port)),	        % remember address for subscriptions
	join(In,Out).


query_sib :-
% query for cached triples, result is inserted in local RDF store
	rdf_retractall(_,_,_),		% clear local RDF store
	
	sib_socket(In,Out),		% get streams
	query(In, Out, Triples),

	insert_triples_in_rdf(Triples).

subscribe_sib :-
	rdf_retractall(_,_,_),

	sib_socket(In,Out),
	subscribe(In,Out, Triples),

	insert_triples_in_rdf(Triples).

unsubscribe_sib :-
	sib_socket(In,Out),
	unsubscribe(In,Out).


% when subscribed to the SIB, process_subscription must be called 
% quite frequently:
% currently, it fails if the SIB sends indications too fast, and more 
% than a single SSAP_message tag is available from the stream (Response
% needs to be partitioned and processed individually). 
% (It can be detected though, and repaired by a simple re-subscribing)
process_subscription :-
	subscription(_,In,_),		% get input stream

	(
	    input_available(In)
	->		
	
	(	
		process_subscription(In, InsertedTriples, RemovedTriples),
		!,
		writeln('+++++'),
		writeln(InsertedTriples),
		writeln('+++++'),
		writeln('-----'),
		writeln(RemovedTriples),
		writeln('-----'),

		insert_triples_in_rdf(InsertedTriples),
		remove_triples_in_rdf(RemovedTriples)
	;
		%process_subscription failed, indicating a problem with XML messages.
		% we try to repair with a query that refills the cache. (a re-subscribe
		% would be better, or fixing the socket/newline issue with SSAP)
		query_sib
	)		
    ;
	true).

input_available(In) :-
	nonvar(In),
	wait_for_input([In],[In],0.000001).

% insert (received) Triples in the local RDF store:
insert_triples_in_rdf([]) :- !.
insert_triples_in_rdf([element(triple_list, [], List)|Rest]) :-
	insert_triples_in_rdf(List),
	insert_triples_in_rdf(Rest),!.
insert_triples_in_rdf([element(triple, [], SPO)|Rest]) :-
	!, %for allowing whitespace etc inbetween, see next clause
%TODO: treatment of literals versus resources
%	member(element(subject, [type=ST],[SV]),SPO),
%	member(element(predicate, [],[PV]),SPO),
%	member(element(object, [type=OT], [OV]),SPO),
%	rdf_assert(type(ST,SV),PV,type(OT,OV)),
	member(element(subject, _,[SV]),SPO),
	member(element(predicate, _,[PV]),SPO),
	member(element(object, _, [OV]),SPO),
	rdf_assert(SV,PV,OV),
	insert_triples_in_rdf(Rest).
insert_triples_in_rdf([_Not_an_element_triple|Rest]) :-
	% skip it for allowing whitespace etc inbetween
	insert_triples_in_rdf(Rest).

% remove (received) Triples in the local RDF store:
remove_triples_in_rdf([]) :- !.
remove_triples_in_rdf([element(triple_list, [], List)|Rest]) :-
	remove_triples_in_rdf(List),
	remove_triples_in_rdf(Rest),!.
remove_triples_in_rdf([element(triple, [], SPO)|Rest]) :-
	!, %for allowing whitespace etc inbetween, see next clause
%TODO: treatment of literals versus resources
%	member(element(subject, [type=ST],[SV]),SPO),
%	member(element(predicate, [],[PV]),SPO),
%	member(element(object, [type=OT], [OV]),SPO),
%	rdf_assert(type(ST,SV),PV,type(OT,OV)),
	member(element(subject, _,[SV]),SPO),
	member(element(predicate, _,[PV]),SPO),
	member(element(object, _, [OV]),SPO),
	rdf_retractall(SV,PV,OV),
	remove_triples_in_rdf(Rest).
remove_triples_in_rdf([_Not_an_element_triple|Rest]) :-
	% skip it for allowing whitespace etc inbetween
	remove_triples_in_rdf(Rest).


insert_sib(triple(S,P,O)) :-
	Triple = element(triple,[],[
                                       element(subject,[],[S]),
				       element(predicate,[],[P]),
				       element(object,[],[O])
				   ]),
	sib_socket(In,Out),
	insert(In,Out,Triple).

insert_sib(ListOfTriples) :-
	structurefromlist(ListOfTriples,TripleStructure),
	Triplelist = element(triple_list,[],TripleStructure),
	sib_socket(In,Out),
	insert(In,Out,Triplelist).

remove_sib(triple(S,P,O)) :-
	Triple = element(triple,[],[
                                       element(subject,[],[S]),
				       element(predicate,[],[P]),
				       element(object,[],[O])
				   ]),
	sib_socket(In,Out),
	remove(In,Out,Triple).

remove_sib(ListOfTriples) :-
	structurefromlist(ListOfTriples,TripleStructure),
	Triplelist = element(triple_list,[],TripleStructure),
	sib_socket(In,Out),
	remove(In,Out,Triplelist).

structurefromlist([],[]):-!.
structurefromlist([triple(S,P,O)|Rest],[ResultHead|ResultTail]) :- 
	ResultHead = element(triple,[],[
                            element(subject,[],[S]),
	                    element(predicate,[],[P]),
	                    element(object,[],[O])
		     ]),
	structurefromlist(Rest,ResultTail).

leave_sib :-
	sib_socket(In,Out),
	leave(In,Out),
	close_connection(In,Out),
	retract(sib(_,_,_)),
	retract(sib_socket(In,Out)),!.
